/**
 * \file
 */
#include "precompiled.hpp"
#include "kfs.hpp"
#include "kfs_file_descriptor.hpp"
#include "kfs_impl.hpp"
#include "kfs_time.hpp"
#include "Exception.hpp"
#include <boost/algorithm/string/classification.hpp>
#include <boost/algorithm/string/iter_find.hpp>
#include <boost/algorithm/string/split.hpp>
#include <boost/scoped_array.hpp>
#include <errno.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <cstring>
#include <fstream>
#include <memory>
#include <time.h>
#include <vector>

using std::cout;  using std::endl;
using std::ios;  using std::fstream;
using std::auto_ptr;  using std::string;
using std::vector;

kfs* HACK_fs;


//---------------------------------- Inode ----------------------------------//

//Inode::Inode()
//:
//    blockNumBytes(512),
//    //numBlocks(8),
//    deviceName("kfs.hex")
//{
//}


//----------------------------------- kfs -----------------------------------//


kfs::kfs(string const& deviceName)
:
    impl(new Impl)
{
    HACK_fs = this;

    impl->f.open(deviceName.c_str(), ios::binary | ios::in | ios::out);
    if(!impl->f)
    {
        // f.rdstate() & (ios::failbit | ios::badbit)
        // TODO How to get obtain the real error code?
        //throw Error(ENOENT, "%s", deviceName.c_str());
        throw Exception("Could not open %s", deviceName.c_str());
    }
    impl->f.exceptions(ios::badbit | ios::failbit | ios::eofbit);

    // Read the super block.
    impl->mb.blockNumBytes = 512;
    boost::scoped_array<uint8_t> block(impl->createBlockBuffer());
    impl->f.read(reinterpret_cast<char*>(block.get()), impl->mb.blockNumBytes);
    impl->deserialize(impl->mb, block.get());
    if(ones32(impl->mb.blockNumBytes) != 1
       || impl->mb.blockNumBytes < 128
       || impl->mb.blockNumBytes > 65536 )
    {
        throw Exception("Refuse to mount this filesystem because it purports "
            "to have %u byte blocks", impl->mb.blockNumBytes);
    }
    // Number of significant bits in a block table index.
    // Example 4KiB block has 1024 pointers of 4 bytes each. Indices are 10
    // bits wide.
    //impl->log_n = floor_log2(impl->mb.blockNumBytes / sizeof(block_index_t)) );
}


kfs::~kfs()
{
}


/*
 * TODO: unlink needs a home. It is logically similar to create.
 */
#include "kfs_cache.hpp"
using boost::scoped_ptr;


void
kfs::unlink(string const& path)
{
    /// The parent directory always has a trailing slash.
    string parentPath;
    /// The child name has no slashes.
    string childName;
    kfs_path_split(path, parentPath, childName);

    /* Open the parent directory in read-write mode. This is necessary to
     * remove the entry from the directory's internal structure.
     */
    scoped_ptr<FileDescriptor> parent(file_open(parentPath, O_RDWR));
    DirIter dirEntry;
    // Find the child's directory entry. Throw if it does not exist.
    impl->dir_find(parent.get(), childName, &dirEntry);
    scoped_ptr<FileDescriptor> child(file_open(path, O_RDWR));
    if(S_ISDIR(child->inodeRef->elt->second->inode.mode))
    {
        // We exclude "." and ".." from the set of entries here.
        //if( impl->dir_numEntries(child.get()) > 2 )
        if( impl->dir_safeToDelete(child.get()) == false )
        {
            throw Error(ENOTEMPTY, 0);
        }
    }
    uint32_t& linkCount = child->inodeRef->elt->second->inode.linkCount;
    if(linkCount == 0)
    {
        // Consistency error: file is referenced by the directory but has zero
        // link count.
        block_index_t blockNum = child->inodeRef->elt->second->blockNum;
        throw Error(EINVAL, "%s: inode %u Consistency error: file is "
            "referenced by the directory but has link count = 0", __FUNCTION__,
            blockNum);
    }
    if(linkCount == 1)
    {
        // No other directories reference the file.
        // Free all data and index blocks.
        child->truncate(0);
    }

    // Decrement the link count.
    --linkCount;
    child->inodeRef->write();

    if(linkCount == 0)
    {
        // No other directories reference the file.  Free the inode.
        block_index_t blockNum = child->inodeRef->elt->second->blockNum;
        // Close the file. Flush inode.
        child.reset();
        // Free the inode block.
        impl->disk_free(blockNum);
    }

    // Remove the file from this directory.
    impl->dir_remove(parent.get(), dirEntry.offset);
}
